Skip to content

docs(skills): parent-path record-currency amounts admit no grouping; data eval for the parent-path currencyField - #33394

Merged
os-zhuang merged 2 commits into
mainfrom
claude/project-thread-z8yej2
Nov 15, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/project-thread-z8yej2

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #32392

domain:skills#1 · os-project-manager · session_01FEkQ5kJc3VU2HmGSE78u8t

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:objectstack-ui 仪表盘规则里关于 currencyMode: 'record' 金额的那句,补上「currencyField 是父路径(如 'invoice.currency_code')的金额,任何分组都不放行」,指向 objectstack-data → Field Types → Currency,并给出两种可用的单币种口径(该金额的 homeAmount 字段,或父对象的 summary);objectstack-data 的 eval 新增一例:发票明细金额跟随发票币种、lookup 路径被拒。
  • 为什么改:旧句只说「按其代码字段分组即可」,作者照着对父路径金额按路径分组,在 main 上会被拒(引擎聚合门与分析 cube 门都拒);而父路径写法上线后 eval 里还没有一例测它。
  • 风险与回滚:纯技能文字与 eval,不动 schema、不动运行时;dashboards.md 改 4 行删 4 行,token 6232 到 6226(付账删了同一注释里两个住在包源码的从句);回滚即还原本 PR。
  • 先看:skills/objectstack-ui/rules/dashboards.md 第 306–311 行;再看 eval 文件末尾第 18 例。
  • 两分钟验证:git diff origin/main -- skills/,预期仪表盘规则只见那一处括号补句与两个删去的从句,eval 只多出第 18 例,其 must_contain 每项都在 expected_output 里。
  • 需要你做的:受管面 Tier H(skills/**):请批准或合并。

What changes

  • skills/objectstack-ui/rules/dashboards.md lines 306–311 (a comment inside the os:check dataset example). "A currencyMode: 'record' amount refuses currency, and sum/avg/min/max unless grouped by its code field" now continues "(a parent-path currencyField admits none, objectstack-data → Field Types → Currency: measure its homeAmount field or the parent's summary)". The data skill's owner text is linked by name, not restated.
  • Paid for in the same comment (token ratchet, headroom 0 before): two clauses whose home is the package source are deleted.
    • "then the column carries currencyField (the per-row code column); a count never does": the result column's wire shape, documented on AnalyticsResult.fields[].currencyField in packages/spec/src/contracts/analytics-service.ts (lines 93–110) and produced at packages/services/service-analytics/src/analytics-service.ts:3666-3693. A dashboard author writes nothing from it.
    • "Not judged: currency over a dynamic field, a fixed code against the tenant's": the measure-currency check's non-judgements, stated in packages/services/service-analytics/src/measure-currency-coherence.ts (module header, "Not judged").
  • skills/objectstack-data/evals/objects-fields-relationships.json eval 18. An invoice whose lines' amount follows the invoice's currency_code through the master_detail (currencyField: 'invoice.currency_code'), the lookup alternative refused, the invoice subtotal a summary that declares its own record currency, and ERR_CURRENCY_CODE_REQUIRED for a blank parent code. must_contain: master_detail, currencyField: 'invoice.currency_code', iso_4217_currency, summary, ERR_CURRENCY_CODE_REQUIRED; must_not_contain: currencyMode: 'fixed', Field.lookup('invoice').
  • .changeset/32392-skills-parent-currency-path-follow-ups.md: '@objectstack/skills': patch, carrying both declaration lines.

Why

The card asked for the sentence to be written to what main does today ("edge 2 may have moved"). It has not moved. Read on origin/main 6538ba4:

  • Engine door. packages/objectql/src/record-currency-aggregate-door.ts:175-181: when the amount's currencyField resolves to a parent path, the door pushes a refusal before it looks at groupBy at all ("No groupBy admits it: a dotted group key is read as a column of this row, which holds no code"). The only exit is the roll-up's own recompute through that same master_detail (ROLLUP_AGGREGATE_OPTION), which no caller can set. The refusal text names the two remedies the new sentence names: a stored home-currency amount field, or a summary rolled up through the master_detail.
  • Test pinning it. packages/objectql/src/record-currency-guards.test.ts:496-519, "an aggregate over a child amount that follows its parent is refused whatever it groups by (ADR-0144 D6, Open item 14)": ungrouped, grouped by invoice.currency_code (string and object form) and grouped by invoice are each refused INVALID_FIELD. The same describe pins the parent's roll-up as one total per currency when grouped by the parent's own code (:526-536).
  • Analytics cube door. packages/services/service-analytics/src/cube-record-currency-door.ts:97-107 admits a grouping only when a dimension's column is the code field on the same object. For a path amount the code field is the dotted invoice.currency_code, while the dimension resolver (analytics-service.ts:4173-4198, assertRecordCurrencyGrouped) answers the last segment of a dimension's sql and the object that segment sits on, so no dimension can match. No analytics test pins a parent-path amount; this reading is from code. The engine door underneath refuses the aggregate regardless.
  • A path amount may declare homeAmount: record-currency-guards.test.ts:686-703 (gp_line.amount with currencyField: 'invoice.currency_code' and homeAmount), so "measure its homeAmount field" is reachable.

Business reason (from the card): an AI author building invoice dashboards over line items reads the dashboards rule; the old sentence led to a refused dashboard, and the data skill's new parent-path rule (PR #32384) had no eval measuring it.

Acceptance notes

  • The order's "package eval-shape check" does not exist for this bundle. ui/scripts/check-skill-eval-tokens.mjs (check:skill-eval-tokens) walks only skills/objectui/evals/*.json (its --list names ten files, none under skills/objectstack-data/), and no other script reads skills/objectstack-*/evals/. The README's shape was checked with a one-off script over the whole file: eval 18 is clean (every must_contain literal in expected_output, no must_not_contain literal in it), and each of its must_contain literals occurs in the data bundle's markdown (1 to 41 occurrences).
  • Pre-existing shape misses in the same file, not touched here: eval 11 (type: 'select', type: 'state_machine'), eval 12 (type: 'summary', type: 'formula') and eval 14 (label: '公司名称', value: 'active') carry must_contain literals absent from their expected_output. Noted, not filed: no gate reads this bundle's evals.
  • Analytics refusal prose for a path amount (read-only inference, not measured). For a parent-path amount the cube door's message would advise "add the dimension over 'invoice.currency_code'", a grouping the same door then refuses again (cube-record-currency-door.ts:109-125). Unmeasured through a public door here; the engine door's own message names the right remedies.

Verification

Head d57adb94 (merge of origin/main 3968a57 after #33427 took eval 17; ours renumbered to 18), first measured on 527f63ef over base 6538ba4; derived gates rerun on d57adb94: 42 exit 0, the same 2 NOT MEASURED.

Line and token budget (skills/**)
  • Net lines in skills/**: +23 / -4. skills/objectstack-ui/rules/dashboards.md: 474 to 474 lines, 24926 to 24902 bytes; token ratchet 6232 to 6226 (ceiling 6232, headroom 6). The first two wordings measured +19 and +13 over; the sentence was shortened and the two package-homed clauses deleted; no ceiling moved, no re-wrap counted as payment.
  • skills/objectstack-data/evals/objects-fields-relationships.json: 296 to 315 lines, 4463 to 4834 tokens (eval fixture, measured, not ratcheted).
  • Whole package, all skills/*/SKILL.md: 4602 to 4602 lines (no SKILL.md touched). Bundle total (whole shipped tree): 244310 to 244675 tokens (base computed from the two per-file deltas, -6 and +371).
  • node scripts/pm/check-skill-line-ratchet.mjs: exit 0. node scripts/check-skills-token-ratchet.mjs: exit 0.
Derived gates: 44 derived, 42 run green, 2 NOT MEASURED

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 44 families for the three paths. Each was run with its exit code captured before any pipe, after building @objectstack/formula and @objectstack/lint under the verify lock (VERDICT command-exit 0, 90s):

  • 42 exit 0, among them check-adr-0087-registration (scan and --self-test), check-changeset-no-major, check-empty-changeset (scan and --self-test), check-skills-token-ratchet (scan and --self-test), @objectstack/lint check:doc-formula-expressions, check:doc-authoring, check:nul-bytes, check:pm-governed-merges, check:published-files, check:skill-compatibility, check:skill-frame-sync, check:skill-identifier-liveness, check:skill-links, check:dts-closure, check:sourcemap-no-sources-content.
  • NOT MEASURED, exit 3 PREREQUISITE NOT MET (each reads every package's dist/): check:dual-build-cjs-loads, check:lean-entry-closure. No package source is touched; CI builds and runs them.
  • --ran reconciliation: "44 derived famil(ies) accounted for — 42 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)".
  • Extra, not derived: pnpm --filter @objectstack/spec check:skill-examples exit 0 after building the @objectstack/client-react closure under the verify lock (VERDICT command-exit 0, 195s): "344 prose examples type-check across 3 surface(s)". That covers the edited os:check block.
  • CI families are read from this PR's check-runs, not re-run here.
Tier and governed readings
  • dispatch-gates --tier: MANDATORY contract-review tier, both skills/** paths under clause ① (the published catalog); no one-line exit for this surface.
  • node scripts/pm/check-governed-merges.mjs --branch claude/project-thread-z8yej2: exit 3, GOVERNED, landing tier H (人合). 2 of 3 paths hit the register (skills/** x2); the changeset is not on it. Size +33 / -4, under the 3000 threshold.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FEkQ5kJc3VU2HmGSE78u8t

…data eval for the parent-path currencyField

The dashboards rule said a record-currency amount refuses sum/avg/min/max
unless grouped by its code field; a master_detail child amount whose
currencyField is the parent path admits no grouping on main (the engine's
aggregate door and the analytics cube door both refuse it). Say so, point to
objectstack-data for the declaration, and name the home field or the parent's
summary as the shapes to measure. Paid for by deleting two clauses whose home
is the package source. Add the objects-fields-relationships eval case for an
invoice whose lines follow its currency.

Claude-Session: https://claude.ai/code/session_01FEkQ5kJc3VU2HmGSE78u8t
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 14, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 527f63ef4d9aaf6aa382592ed64cf02fea908e2f
Local-runs: none
Scope: skills/objectstack-data/evals/objects-fields-relationships.json, skills/objectstack-ui/rules/dashboards.md + none

Inputs read: card #32392 (body, triage, claim, os-dev-report), PR #33394 (body, three-file list, net diff against merge-base 6538ba48fc4590402cd6280cf83a8902f6de83d3), the check-runs on the head. Cited code read on origin/main at b63c894934b5e427b456b997774f48a220edb7e5; none of the cited files moved between the merge-base and that tip. No file in the diff implements or enforces the two hits (no gate, schema or package source is touched), so the + set is empty. The changeset is the lane seat's ACCEPT.

① Derived judgments

  • Accept-set: none moves. No schema, no packages/** source, no gate, no error code. Right.
  • Public surface, @objectstack/skills (the shipped catalog; packages/skills/scripts/sync-catalog.mjs copies the whole skills/** tree, evals included):
    • objectstack-ui/rules/dashboards.md now says a currencyMode: 'record' amount whose currencyField is a parent path admits no grouping, and names two things to measure instead. Right. This brings the skill into line with main: packages/objectql/src/record-currency-aggregate-door.ts (assertRecordCurrencyAggregatesGrouped) pushes a path amount as a hit before it looks at grouped; the one exit is ROLLUP_AGGREGATE_OPTION, which is module-private. record-currency-guards.test.ts, in the describe "an aggregate over a child amount that follows its parent is refused whatever it groups by", pins all four shapes (ungrouped; 'invoice.currency_code' as a string and in object form; 'invoice') as INVALID_FIELD.
    • Two clauses are removed from the same comment: what the result column carries (currencyField; a count never does) and the "Not judged" pair. Right. Neither clause tells a dashboard author to write anything. The column shape is documented on AnalyticsResult.fields[].currencyField in packages/spec/src/contracts/analytics-service.ts, which also says "A count / count_distinct over the amount reads no amount and carries neither key". No other skill file or eval cites the removed text, so nothing is orphaned (checked with grep at the head).
    • objectstack-data evals: eval 17 is added. Right. It is the case the card's item 2 asks for.

② Semver level

  • The changeset '@objectstack/skills': patch matches the diff: a documentation sentence and one eval fixture in a package of the fixed group, with no API, schema or runtime change. Right.
  • Clause-②: no is on the PR body and the changeset, as the card's surface line requires. Metadata-protocol: none is on both too. Right: no key, value or metadata type an author writes changes.

③ Boundary flags

  • open_questions: empty. Nothing to answer.
  • Dev flag 1: no eval-shape check exists for this bundle. Confirmed by reading. My own literal check over the file at the head: eval 17 has all five must_contain literals in expected_output (master_detail, currencyField: 'invoice.currency_code', iso_4217_currency, summary, ERR_CURRENCY_CODE_REQUIRED). Neither must_not_contain literal appears in it (currencyMode: 'fixed', Field.lookup('invoice')). Answered, holds.
  • Dev flag 2: earlier shape misses in the same file. Confirmed independently. Eval 11 lacks type: 'select' and type: 'state_machine'; eval 12 lacks type: 'summary' and type: 'formula'; eval 14 lacks label: '公司名称' and value: 'active'. Each is in that eval's must_contain but absent from its expected_output. They predate this PR and this diff does not touch them. Escalated to the seat as a follow-up card for domain:skills (carrier none today).
  • Dev flag 3: the analytics cube door's remedy for a path amount. Confirmed by reading packages/services/service-analytics/src/cube-record-currency-door.ts on main. For a measure over a path amount, codePath is the dotted invoice.currency_code. The message advises "add the dimension over 'invoice.currency_code'". The resolver in analytics-service.ts (assertRecordCurrencyGrouped) reduces a dimension to its last segment, so that dimension can never match the dotted code field: the remedy names a grouping the same door refuses again. This is outside the diff and not reviewed here. Escalated to the seat as a follow-up card for the analytics lane. This PR's sentence names the correct remedies, the same two the engine door's own message names.
  • Dev flag 4: process deviations. The thread ran below the mandated tier; this record is the review at CONTRACT_REVIEW_TIER, which closes that flag. Neither the HTML-comment signature line, the empty assignees, nor the order of the changeset's declaration lines touches the contract. They are the seat's to note.

④ Design judgment

skills/objectstack-ui/rules/dashboards.md (lines 306 to 311 at the head), judged against the card's item 1 and main:

  • Claim: a parent-path currencyField admits no grouping. Holds at the engine door, as cited in ①. It also holds at the cube door: assertRecordCurrencyAggregatesGrouped in cube-record-currency-door.ts admits only when d.object === target.object && d.column === codeField. The analytics resolver hands it currencyConfig.currencyField as is (dotted), and a dimension column is a last segment, so no dimension matches. The draft-preview branch calls the same judgment. I read three attempts to break the claim against the code: a groupBy of the master_detail itself, a dotted dimension sql, and a parent-object dimension over currency_code. Each is refused on main: the engine door never consults groupBy for a path amount, and no dimension column equals the dotted code field.
  • Claim: measure its homeAmount field. Holds. homeAmount is admitted on a path amount: gp_line.amount in record-currency-guards.test.ts declares currencyField: 'invoice.currency_code' with homeAmount. The home field is dynamic, so neither door judges it. The cube door's own remedy for a record amount says the same.
  • Claim: measure the parent's summary. Holds. The admitted roll-up through the very master_detail registers (ruling A in the record-currency-aggregate-door.ts header). The same describe pins it as one total per currency when grouped by the parent's own currency_code.
  • Card constraints. One sentence: one parenthesis, met. No restatement of the owner text: the parenthesis points to objectstack-data, Field Types, Currency. That spelling matches the data skill's SKILL.md link label "Field Types" and the H3 "Currency" in rules/field-types.md, the convention field-types.md already uses back ("objectstack-ui → Dashboards"). The pointer sits inside an os:check TypeScript block, where a Markdown link would not render.
  • The two deletions. No guidance an author acts on is lost (see ①).

skills/objectstack-data/evals/objects-fields-relationships.json (eval 17), judged against the card's item 2 and main:

  • Prompt. It matches the scenario the card names: invoice lines whose amounts follow the invoice's currency, with the lookup path refused.
  • expected_output, sentence by sentence against rules/field-types.md (Currency) and the code on main:
    • "one hop to the parent's iso_4217_currency field": isRecordCurrencyCodeField in packages/spec/src/data/record-currency-path.ts.
    • "A lookup path is refused": refuseDishonestRecordCurrencyPath in object.zod.ts, segment 1 must be a master_detail.
    • "subtotal ... must declare its own currencyMode: 'record' with currencyField: 'currency_code'": collectRecordCurrencyPathErrors in stack.zod.ts, arm (b). A roll-up through that very master_detail that does not declare the parent's code field in record mode is an error.
    • "A blank invoice code refuses the line's amount (ERR_CURRENCY_CODE_REQUIRED)": the validate() test in record-currency-guards.test.ts pins that code at invoice.currency_code.
    • "currency_code cannot change until none does": the owner text in field-types.md.
    • Every sentence holds.
  • Shape. Clean (see ③). Ids run 1 to 17 with no gap.

Findings

  • must-fix: none.
  • should-fix: none.
  • nit N1 (dashboards.md): "or the parent's summary" reads as a second consolidated figure. The summary is itself a record-mode amount, so a sum or avg over it still needs a grouping by the parent's currency_code; only homeAmount gives one figure. The sentence's main clause already states that rule for every record amount, and the data skill's owner text is one pointer away, so correctness holds. Accepted as is, or a follow-up on the prose face.
  • nit N2 (eval 17):
    • must_not_contain Field.lookup('invoice') would also fail an answer that quotes the teammate's refused declaration in order to reject it. That is the file's existing pattern (evals 5 and 7 forbid overrides: the same way), so it is accepted.
    • The must_contain literal summary is weak (the prompt's word "subtotal" invites it anyway). It discriminates little but is harmless.
  • nit N3 (skills/objectstack-data/evals/README.md, outside the diff): the per-file topic list for objects-fields-relationships.json does not yet mention record currency. Optional follow-up on the prose face.

Nits alone end the round.

Gates (the head's check-runs are the verdicts): 48 check-runs, none pending: 30 success, 18 skipped, 0 failure, cancelled or timed out. The lint families (repo and tooling, CI release and engine, docs and code guards, ESLint), the type-check families and the core test shards are all green. Nothing red.

Implemented-by: claude/project-thread-z8yej2
Reviewed-by: session_01FEkQ5pjSPT7ui2EUcunaVb

VERDICT: PASS

Adopted by the seat 2026-11-14T18:43Z · isolated sub-agent at CONTRACT_REVIEW_TIER · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

维护者速读

  • 改了什么:skills/objectstack-ui/rules/dashboards.md 第 306–311 行(os:check 块内注释)加一句:currencyField 为父路径的金额不接受任何分组,指向 objectstack-data 的 Currency 规则,替代做法是度量其 homeAmount 字段或父记录的 summary;为守 token 上限删去两条本该住在包源码里的从句(6232 → 6226)。objects-fields-relationships.json 新增 eval 17(发票行跟随发票币种,lookup 写法被拒)。外加一个 patch changeset。
  • 为什么改:旧句"按其币种字段分组则可"会让作者按路径分组,而 main 上引擎门在读 groupBy 前就拒绝路径金额(record-currency-aggregate-door.ts,record-currency-guards.test.ts 钉住四种分组);数据技能的父路径规则此前没有 eval。
  • 风险与回滚:只动技能文字与一个 eval 样例,不动 schema、运行时与门禁;回滚即还原本 PR。
  • 先看:skills/objectstack-ui/rules/dashboards.md 第 306–311 行,再看 eval 文件末尾的 eval 17。
  • 两分钟验证:git diff origin/main -- skills/,只见这两处与 changeset。
  • 需要你做的:受管面 Tier H(skills/**),契约复审 PASS 评论 6882437074(Head-sha 527f63ef4d9aaf6aa382592ed64cf02fea908e2f)已在案,请批准或直接合并。
  • 席位意见:可以合。新句与 eval 17 的每条断言都对得上 main 代码;复审顺带发现分析服务的拒绝提示自相矛盾,另立卡。

席位核对

Verdict: ACCEPT, held for the maintainer · head 527f63ef4d9aaf6aa382592ed64cf02fea908e2f · card #32392 · report 6882194334 · contract review 6882437074 (PASS on this head, CONTRACT_REVIEW_TIER; Ruling-ref for the in-seat review: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb option A).

  • Shape: draft → main, Fixes #32392. skills/objectstack-ui/rules/dashboards.md (+4 / -4), skills/objectstack-data/evals/objects-fields-relationships.json (+19), one '@objectstack/skills': patch changeset. GOVERNED, Tier H (skills/**). Clause-②: no; Metadata-protocol: none.
  • Order assumption falsified, accepted: no repo check reads the shape of skills/objectstack-*/evals (check:skill-eval-tokens covers objectui only). The dev checked eval 17 by script and the review re-checked it: all five must_contain literals are in expected_output, neither must_not_contain literal is.
  • Changeset (TIER_DEFAULT): correct level for a docs sentence and an eval fixture.
  • Gates: 44 derived, 42 exit 0, 2 NOT MEASURED (dual-build-cjs-loads, lean-entry-closure; no package touched). Token ratchet 6226, no raise. Check-runs on the head: 48, 30 success, 18 skipped, none failing. Mergeable clean.
  • Accepted deviations (review nits): N1, "the parent's summary" is itself a record-currency amount, so only homeAmount gives one consolidated figure (the main clause already covers it); N2, eval 17's must_not_contain literal would also fail an answer that quotes the refused declaration to reject it (same pattern as evals 5 and 7), and its summary literal is weak; N3, the evals README topic list does not name record currency yet. Process: the thread ran on the session model and the tier was met by this in-seat review.
  • Follow-ups the seat files: (1) analytics lane: cube-record-currency-door.ts advises grouping by 'invoice.currency_code' for a parent-path measure, then refuses that grouping because the resolver keeps only a dimension's last segment; (2) domain:skills: evals 11, 12 and 14 of the same file already miss must_contain literals in their expected_output.
  • Landing: Tier H. The seat does not flip this PR ready or enqueue it. It needs the maintainer's merge, or an authorised APPROVED review after which the seat lands it.

@objectstack-fleet objectstack-fleet Bot added the pm:awaiting-merge PR only: landing waits on the maintainer's own review and merge (SKILL.md 复核 ②); never on an issue label Nov 14, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review November 14, 2026 21:27
@os-zhuang
os-zhuang enabled auto-merge November 14, 2026 21:27
@os-zhuang
os-zhuang added this pull request to the merge queue Nov 14, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Nov 14, 2026
…-z8yej2

# Conflicts:
#	skills/objectstack-data/evals/objects-fields-relationships.json
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Seat delta check — PR #33394 @ d57adb94d63ec922848c817131d50e28bfe8b7b9

Verdict: ACCEPT stands on the new head (Tier H, held for the maintainer). It covers ACCEPT 6882448209 and contract review 6882437074, both on 527f63ef.


Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Nov 14, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Correction: contract-review tier

The contract-review record(s) 6882437074 on this PR say Served-tier: CONTRACT_REVIEW_TIER, and the seat adopted them as "isolated sub-agent at CONTRACT_REVIEW_TIER". That is wrong. The seat launched the reviewing sub-agent without the tier, so it ran at the seat's own default tier, not at CONTRACT_REVIEW_TIER. The seat found this on its own audit. The review was still isolated and adversarial, and its findings stand as an off-tier review. They are not the at-tier record AGENTS.md and the in-seat review ruling require.

The seat re-runs the review at CONTRACT_REVIEW_TIER on this PR's head and posts that record here. Until it does, this PR has no at-tier record.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d57adb94d63ec922848c817131d50e28bfe8b7b9
Local-runs: none
Scope: skills/objectstack-data/evals/objects-fields-relationships.json, skills/objectstack-ui/rules/dashboards.md + none

Inputs read: card #32392 (body and all four comments), PR #33394 (body, three-file list, net diff against main), the check-runs on the head. Cited code read on origin/main at 8e63ed877771d6f3492309e596fb5422b3f0898e; the two hits files differ from that tip by this diff alone (dashboards.md +4 / -4, the evals file +19). No file in the diff implements or enforces the two hits (no gate, schema or package source is touched), so the + set is empty. The changeset is the lane seat's ACCEPT at TIER_DEFAULT. This is the at-tier re-review: the earlier record 6882437074 and the seat delta check 6884866541 were not used as evidence; every reading below is from the tree.

① Derived judgments

  • Accept-set: nothing moves. No schema, no packages/** source, no gate, no error code. Right.
  • Public surface, @objectstack/skills (the shipped skills/** tree, evals included):
    • objectstack-ui/rules/dashboards.md gains one parenthesis: a parent-path currencyField admits no grouping; measure the amount's homeAmount field or the parent's summary. Right. It brings the rule into line with main (④).
    • The same comment loses two clauses: "then the column carries currencyField (the per-row code column); a count never does", and "Not judged: currency over a dynamic field, a fixed code against the tenant's". Right. Neither tells a dashboard author to write anything. The first is the result column's wire shape (enrichResultColumns in packages/services/service-analytics/src/analytics-service.ts); the second is the non-judgement list in the header of measure-currency-coherence.ts. No other file under skills/** at the head cites either clause, and the pointer back from objectstack-data/rules/field-types.md ("A dataset measure's currency (refused DATASET_INVALID): objectstack-ui → Dashboards") still lands on text that says so.
    • objectstack-data evals gain eval 18. Right. It is the case card item 2 asks for; evals 1 to 17 are untouched and ids run 1 to 18 with no gap.

② Semver level

  • '@objectstack/skills': patch matches what the diff publishes: one documentation sentence and one eval fixture, in a package of the fixed group, with no API, schema or runtime change. Right.
  • Clause-②: no and Metadata-protocol: none are on both the PR body and the changeset, as the card's surface line requires. Right: no key, value or metadata type an author writes changes meaning or acceptance.

③ Boundary flags

  • open_questions: empty. Nothing to answer.
  • Dev flag: no eval-shape check reads this bundle. Answered by a literal check of the file at the head: eval 18 carries all five must_contain literals in expected_output and neither must_not_contain literal. Holds.
  • Dev flag: older shape misses in the same file. Confirmed: eval 11 (type: 'select', type: 'state_machine'), eval 12 (type: 'summary', type: 'formula') and eval 14 (label: '公司名称', value: 'active') carry must_contain literals absent from their expected_output. They predate this PR and the diff does not touch them. Escalated to the seat as a domain:skills follow-up.
  • Dev flag: the cube door's remedy for a path amount. Confirmed by reading cube-record-currency-door.ts on main: for a path amount codePath is the dotted invoice.currency_code, and the message says "add the dimension over" that path, a grouping the same door cannot admit. Outside the diff, read and not reviewed. Escalated to the seat as an analytics-lane follow-up.
  • Dev flag: process deviations (thread model below the tier, the signed HTML comment, no assignees, declaration lines after the changeset title). None touches the contract; this record is the review at tier.
  • The earlier records (6882437074 on 527f63ef, delta 6884866541). Read only to answer them. Their must-fix and should-fix lists were empty, so no fix was owed on this head. Their three nits are confirmed below as N1 to N3; none is rejected. Their two escalations are the two confirmed above. The delta's claim that only the eval id moved (17 to 18) agrees with the tree: the eval body matches the PR's description and evals 1 to 17 equal main.

④ Design judgment

skills/objectstack-ui/rules/dashboards.md (lines 306 to 313 at the head), against card item 1 and main:

  • "a parent-path currencyField admits none". Holds at both doors.
    • Engine: assertRecordCurrencyAggregatesGrouped in packages/objectql/src/record-currency-aggregate-door.ts resolves the path with recordCurrencyPathOf and pushes the amount as a hit before grouped is consulted. The one exit is rollupVia === path.via, fed by the module-private ROLLUP_AGGREGATE_OPTION. record-currency-guards.test.ts pins ungrouped, 'invoice.currency_code' (string and object form) and 'invoice' as INVALID_FIELD.
    • Analytics: assertRecordCurrencyGrouped hands the cube door currencyConfig.currencyField as declared (the plugin's sourceFieldMeta relays it verbatim under record), so codeField is dotted. A dimension resolves to its last segment on the hopped object, and the door admits only d.object === target.object && d.column === codeField. Three attempts to break it, read against the code: a dimension over invoice.currency_code (column currency_code on invoice, wrong on both tests), a dimension over the master_detail itself (column invoice), a bare currency_code dimension on the line (the line holds no such column, and the column is not the dotted code field). None is admitted.
  • "measure its homeAmount field". Holds. A path amount may declare homeAmount and the projection fills it (gp_line in record-currency-guards.test.ts: amount_home stored for a line following invoice.currency_code). The home field is a dynamic currency, which neither door judges.
  • "or the parent's summary". Holds as a thing that may be measured: the roll-up through that very master_detail registers (ruling A, the door's header). See N1 for what it leaves unsaid.
  • Card constraints. One sentence: one parenthesis. No restatement of the owner text: it points to objectstack-data → Field Types → Currency, which is the ### Currency section of rules/field-types.md holding "A line in its parent's currency", in the spelling that file uses for its pointer back. "True to main when picked up": edge 2 has not moved at 8e63ed87.
  • What stays. The sentence's lead ("refuses currency, and sum/avg/min/max unless grouped by its code field") is unchanged and still true for a sibling code field. The block is a comment inside an os:check example; the type-check families on the head are green.

skills/objectstack-data/evals/objects-fields-relationships.json (eval 18), against card item 2 and main:

  • Prompt. The scenario the card names: invoice lines whose amounts follow the invoice's currency, the lookup alternative refused.
  • expected_output, sentence by sentence:
    • "currencyMode: 'record' and currencyField: 'invoice.currency_code' ... one hop to the parent's iso_4217_currency field": refuseDishonestRecordCurrencyPath (packages/spec/src/data/object.zod.ts) admits exactly two segments starting at a master_detail; isRecordCurrencyCodeField (record-currency-path.ts) requires valueDomain: 'iso_4217_currency' on segment 2. Holds.
    • "A lookup path is refused": the same function, the viaField.type !== 'master_detail' arm, with a message written for lookup. Holds.
    • "subtotal is a summary with function: 'sum' ... must declare its own currencyMode: 'record' with currencyField: 'currency_code'": collectRecordCurrencyPathErrors (stack.zod.ts), arm (b), refuses any other declaration, a missing one included. Holds.
    • "A blank invoice code refuses the line's amount (ERR_CURRENCY_CODE_REQUIRED)": CURRENCY_CODE_REQUIRED_CODE in validation/record-currency-code.ts, pinned at invoice.currency_code by the guards test. Holds.
    • "the invoice's currency_code cannot change until none does": record-currency-parent-lock.ts. Holds.
  • Agreement with the skill it measures. Every claim is taught in rules/field-types.md (Currency), and each must_contain literal occurs in that section, so the eval measures the rule and not outside knowledge.
  • Shape. The README's: id, prompt, expected_output, files, assertions; literals clean (③).

Findings

  • must-fix: none.
  • should-fix: none.
  • nit N1 (dashboards.md): "or the parent's summary" does not say that the roll-up is itself a record-mode amount. A sum of it over invoices is refused unless grouped by the invoice's currency_code (pinned in the guards test: "refused ungrouped, one total per currency grouped by its code"); only homeAmount gives one consolidated figure. The sentence states nothing false: its own lead clause gives that rule for every record amount, the summary's author had to declare currencyMode: 'record' on it, and the engine's refusal names the remedy. Accepted deviation, or a follow-up on the prose face (the file has 6 tokens of headroom).
  • nit N2 (eval 18): the must_contain literal master_detail fails an answer written only in the builder spelling the owner text itself shows (Field.masterDetail('invoice')), and must_not_contain Field.lookup('invoice') fails an answer that quotes the teammate's declaration in order to refuse it. Both follow the file's existing pattern (evals 2, 8 and 17 require master_detail). summary discriminates little. Accepted.
  • nit N3 (skills/objectstack-data/evals/README.md, outside the diff): the topic list for this file does not name record currency. Optional follow-up on the prose face.

Nits alone end the round.

Gates (the head's check-runs are the verdicts): 48 check-runs, none pending: 30 success, 18 skipped, none failed, cancelled or timed out. The lint, type-check, core test, changeset, PR-body guard and governed-surface queue guard families are green.

Implemented-by: claude/project-thread-z8yej2
Reviewed-by: session_01FEkQ5pjSPT7ui2EUcunaVb

VERDICT: PASS

Adopted by the seat 2026-11-15T00:30Z · isolated sub-agent at CONTRACT_REVIEW_TIER (served model verified by the seat) · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z

Merged via the queue into main with commit 6e23180 Nov 15, 2026
48 checks passed
@os-zhuang
os-zhuang deleted the claude/project-thread-z8yej2 branch November 15, 2026 00:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation pm:awaiting-merge PR only: landing waits on the maintainer's own review and merge (SKILL.md 复核 ②); never on an issue size/s tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

skills: the master_detail parent-currency path owes two follow-ups (ui dashboards aggregate rule, data eval case)

3 participants